/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Nvigorate.Common;
using Nvigorate.Common.Reflection;
using Nvigorate.Data.Query;
using Nvigorate.Extensions;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.Provider;
using Nvigorate.Relational.Repository;

namespace Nvigorate.Relational.Builders
{
    public class ManyToManyRelationshipBuilder
    {
        protected IRepositoryMapIndex _repositoryMapIndex;
        protected IPersistenceKeyManager _keyManager;
        protected IPersistable _parent;
        protected IEnumerable _children;
        protected IRelationshipChanges _changes;
        protected ManyToManyRelationship _relationship;

        protected IEnumerable<Tuple<string, Variable, Variable>> GetKeyValues(object instance)
        {
            int keyIndex = 0;
            var subjectKeys =
                _relationship
                    .Container
                    .SubjectPairs
                    .Select(p =>
                        {
                            return Tuple.Create(
                                p.Source,
                                Variable.Named("key" + ++keyIndex),
                                Variable.Named("key" + keyIndex)
                                    .Set(GetKeyValueForChild(_parent.Instance, p))
                                );
                        }
                    );

            var relativeKeys =
                _relationship
                    .Container
                    .RelativePairs
                    .Select(p =>
                        {
                            return Tuple.Create(
                                p.Source,
                                Variable.Named("key" + ++keyIndex),
                                Variable.Named("key" + keyIndex)
                                    .Set(GetKeyValueForChild(instance, p))
                                );
                        }
                    );

            return subjectKeys.Concat(relativeKeys);
        }

        protected IEnumerable<Column> InsertColumns
        {
            get
            {
                Func<RelationPair, Column> createColumn =
                    p => Column.Named(p.Source).OwnedBy(_relationship.Container.ContainerName);
                var relativeColumns = _relationship.Container.RelativePairs.Select(createColumn);
                var subjectColumns = _relationship.Container.SubjectPairs.Select(createColumn);
                return subjectColumns.Concat(relativeColumns);
            }
        }

        protected IEnumerable<IClause> BuildDeleteClause(object instance, IEnumerable<Tuple<string, Variable, Variable>> keyList)
        {
            return keyList
                .Select(k => Criterion.Column(k.Value1, _relationship.Container.ContainerName).EqualTo(k.Value2).And())
                .Cast<IClause>();
        }

        protected QueryList DeleteRemovedRelationships
        {
            get
            {
                return _changes
                    .GetRemovedRelatives()
                    .Select(d =>
                        {
                            var keyList = GetKeyValues(d).ToList();
                            return new QueryList()
                                       {
                                           keyList.Select(k => k.Value3).ToQueryList(),
                                           new Delete(
                                               Table.From(_relationship.Container.ContainerName),
                                               Where.Criteria(
                                                   new CriteriaClause(BuildDeleteClause(d, keyList))
                                                   )
                                               ),
                                       };
                         
                        }).ToQueryList();
            }
        }

        protected QueryList InsertAddedRelationships
        {
            get
            {
                var source = _changes == null ? _children : _changes.GetAddedRelatives();
                if(source == null)
                    return new QueryList();
                return source.Select(a =>
                    {
                        var keyList = GetKeyValues(a).ToList();
                        return new QueryList()
                                   {
                                       keyList.Select(k => k.Value3).ToQueryList(),
                                       Insert.Query(
                                           Table.From(_relationship.Container.ContainerName),
                                           InsertColumns.ToQueryList(),
                                           keyList.Select(k => k.Value2).ToQueryList()
                                           )
                                   };
                    }
                    ).ToQueryList();
            }
        }

        protected IQueryObject GetKeyValueForChild(object instance, RelationPair pair)
        {
            var persistable = GetPersistableForInstance(instance);
            return persistable == null
                       ? (IQueryObject)
                         new Literal(Reflector.Read(instance, pair.Target)):
                                                                               _keyManager.GetKeySelectStatement(instance.GetType(), pair.Target, persistable);
        }

        protected IPersistable GetPersistableForInstance(object instance)
        {
            return _keyManager.Persisted.Where(p => ReferenceEquals(p.Value.Instance, instance)).Select(p => p.Value).FirstOrDefault();
        }
        
        public QueryList GetQuery()
        {
            return _changes == null ?
                                        InsertAddedRelationships :
                                                                     GetQueryFromChanges();
        }

        protected QueryList GetQueryFromChanges()
        {
            return new QueryList()
                .Add(InsertAddedRelationships)
                .Add(DeleteRemovedRelationships);
        }

        public ManyToManyRelationshipBuilder(IEnumerable list, IPersistenceKeyManager keyManager, IPersistable parent, IRepositoryMapIndex repositoryMapIndex, ManyToManyRelationship relationship)
        {
            _children = list;
            _changes = list as IRelationshipChanges;
            _keyManager = keyManager;
            _parent = parent;
            _repositoryMapIndex = repositoryMapIndex;
            _relationship = relationship;
        }
    }
}